package io.hellsinger.vortex.service.storage.transaction

import androidx.core.graphics.drawable.toBitmap
import io.hellsinger.filesystem.attribute.DefaultPathAttribute
import io.hellsinger.filesystem.attribute.PathType
import io.hellsinger.filesystem.linux.attribute.attributes
import io.hellsinger.filesystem.linux.attribute.attributesOrNull
import io.hellsinger.filesystem.linux.directory.LinuxDirectoryEntry
import io.hellsinger.filesystem.linux.directory.asLinuxDirectory
import io.hellsinger.filesystem.linux.directory.treeWalker
import io.hellsinger.filesystem.linux.error.ErrnoException
import io.hellsinger.filesystem.linux.error.LinuxOperationErrors
import io.hellsinger.filesystem.linux.file.asLinuxFile
import io.hellsinger.filesystem.linux.operation.create
import io.hellsinger.filesystem.linux.operation.sendFileFlow
import io.hellsinger.filesystem.path.Path
import io.hellsinger.vortex.data.model.OperationState
import io.hellsinger.vortex.data.model.PathItem
import io.hellsinger.vortex.foundation.logger.logIt
import io.hellsinger.vortex.service.model.Dest
import io.hellsinger.vortex.service.model.Source
import io.hellsinger.vortex.ui.icon.Icons
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect

class CopyTransaction(
    private val sources: List<Source>,
    private val dest: Dest,
    private val writeSizeBuffer: Int = DEFAULT_BUFFER_SIZE,
) : StorageTransaction() {
    override suspend fun perform(): Result {
        for (i in 0 until sources.size) {
            val source = sources[i]
            when (source.type) {
                PathType.File -> {
                    val resolved =
                        if (dest.path.attributesOrNull?.type == PathType.Directory) {
                            PathItem(
                                path = dest.path resolve source.path.getNameAt(),
                                attrs = DefaultPathAttribute(type = PathType.Directory),
                            )
                        } else {
                            PathItem(
                                owner = dest,
                                attrs = dest.attributes,
                            )
                        }

                    resolveFileToFileCopy(PathItem(source), resolved, i)
                }

                PathType.Directory -> resolveDirToDirCopy(PathItem(source), PathItem(dest), i)
            }
        }

        closeNotificationAfter(DEFAULT_NOTIFICATION_TIMEOUT)

        return Result.Success
    }

    private suspend fun resolveFileToFileCopy(
        source: PathItem,
        dest: PathItem,
        index: Int,
    ) {
        source.path
            .asLinuxFile()
            .sendFileFlow(dest.path, writeSizeBuffer)
            .collect()
        updateOperationState {
            OperationState.Transition(
                id = id,
                type = TransactionTypes.COPY,
                progress = index + 1L,
                max = sources.size.toLong(),
                source = source,
                dest = dest,
            )
        }
        updateNotification {
            setContentTitle("Copy operation")
            setSmallIcon(io.hellsinger.vortex.icon.R.drawable.ic_folder_24)
            setLargeIcon(Icons.Rounded.Copy?.toBitmap())
            setContentText("${source.path.getNameAt()} copied to ${dest.path.getNameAt()}")
        }
    }

    private suspend fun resolveDirToDirCopy(
        source: PathItem,
        dest: PathItem,
        index: Int,
    ) {
        val resolveSourceDir = (dest.path resolve source.path.getNameAt()).logIt()

        // TODO: Implement 'merge'
        // If resolved-source doesn't exist - create it
        if (resolveSourceDir.attributesOrNull == null) resolveSourceDir.asLinuxDirectory().create()

        source.path
            .asLinuxDirectory()
            .treeWalker()
            .catch { error ->
            }.collect { entry ->
                entry as LinuxDirectoryEntry<Path>

                val sourceEntry = PathItem(entry.path, entry.attributes)

                val resolvedPath =
                    resolveSourceDir resolve source.path.relativize(entry.path).subPath(1)
                val resolved = PathItem(resolvedPath, entry.attributes)

                when (sourceEntry.type) {
                    PathType.Directory ->
                        try {
                            resolvedPath.asLinuxDirectory().create()
                            updateOperationState {
                                OperationState.Transition(
                                    id = id,
                                    type = TransactionTypes.COPY,
                                    progress = index + 1L,
                                    max = sources.size.toLong(),
                                    source = source,
                                    dest = dest,
                                )
                            }
                        } catch (exception: ErrnoException) {
                            if (exception.number != LinuxOperationErrors.E_EXIST) {
                            }
                            // Ignore
                        }

                    PathType.File -> resolveFileToFileCopy(source, resolved, index)
                }
            }
    }

    companion object {
        const val SIZE_PARAM_KEY = "buffSize"
        const val SIZE_PARAM_DEFAULT_VALUE = DEFAULT_BUFFER_SIZE
    }
}
